/**
 *	Yggdrasil World Framework Toolkit
 *	(A component of the NeuroCoSA toolkit)
 *	Copyright (C) 2001-5 Stuart Meikle.
 *
 *	This is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU Lesser General Public
 *	License as published by the Free Software Foundation; either
 *	version 2.1 of the License, or (at your option) any later version.
 *
 *	This library is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *	Lesser General Public License for more details.
 *
 * @author	Stuart Meikle
 * @version	2006-halloween
 * @license	LGPL
 */

package org.stumeikle.Yggdrasil;

//
// making more generic...
// silly name for today
// 20031104 stumeikle
//

//Nervous system is a conduit for the actions and senses
//(to and from the brain)

import java.text.*;
import java.net.*;
import java.io.*;
import org.stumeikle.NeuroCoSA.DataBlock;

public class NervousSystem
{
    Socket		controlSocket 	= null;
    Socket		senseSocket 	= null;
    ServerSocket	actionSSocket	= null;
    Socket		actionSocket	= null;
    ObjectInputStream	oipStream;
    ObjectOutputStream  oopStream;
    int			commPort;
    SenseWatcher	sw = null;
    DataBlock		thesenses = null;
    long		lastSenseV= 1;
    DataBlock		theactions= null; //could be dragon actions now
    String		name=null;
    String 		hostname;
    int			ctrlPort;
			//cache for the incoming senses

    public NervousSystem(String n, DataBlock a)
    {
        name = n;
	theactions = a;
	
	//set some defaults
	ctrlPort = 9999;
	hostname = new String("localhost");
    }


    public void setCtrlPort (int i )
    { ctrlPort =i ; }	

    public void setServerName( String s )
    { hostname = s; } 

    public void	connect() throws Exception
    {
	//bind to the control port
	try
	{
	    BindToSocket( ctrlPort ); //IMPROVE WARNING: this should be const!

	    //20031103 send the creature type to the server. use the name
	    PrintWriter out = new PrintWriter(controlSocket.getOutputStream(), true);
            out.println( name );
	    //out.close();
	}
	catch (UnknownHostException e)
	{
	    System.out.println( name + ":");
	    System.out.println( name + ":! Failed to find localhost");
	    System.out.println( name + ":");
	    //System.exit(1);
	    throw (e);
	}
	catch (IOException e)
	{
	    System.out.println( name + ":");
	    System.out.println( name + ":! Unable to bind to socket "+ctrlPort);
	    System.out.println( name + ":" + e.getMessage());
	    //System.exit(1);
	    throw (e);
	}

	System.out.println(name + ":");
	System.out.println(name + ":*** Successfully created and bound to socket ***");
	System.out.println(name + ":");

	try
	{
	    GetCommPort();
	}
	catch(Exception e)
	{
	    System.out.println(name + ":Failed to get assigned comm channel");
	    System.out.println(name + ":");
	}

        System.out.println(name + ": communicating on port " + commPort);

	//close the control socket and rebind on the given port
	try{controlSocket.close();}catch(Exception e){}

	try
	{
	    BindToSensesAndActions();
	}
	catch (IOException e)
	{
	    System.out.println(name + ":");
	    System.out.println(name + ":! server not responding on given ports");
	    System.out.println(name + ": Error =" + e.getMessage());
	    //System.exit(1);
	    throw(e);
	}

	try
	{
	    oopStream = new ObjectOutputStream( actionSocket.getOutputStream() );
	}
	catch(Exception e)
	{
	    System.out.println(name + ":! problem creating output stream");
	}

	//could spawn a wee thing here to watch for incoming sense info
	//(later)
	thesenses= new DataBlock();
	//theactions=new Actions();set above ...
	sw = new SenseWatcher( senseSocket );
	sw.start();
    }


//	try
//	{
//	    GetSenses_block();
//	    System.out.println("ELF: successfully got senses");
//	}
//	catch( Exception e )
//	{
//	    System.out.println("ELF:! got an object but i dunno what it is or ioerror!");
//	}


    public void shutDown()
    {
        //we could send a control message here so that the server
	//stops our session

	try{
	oipStream.close();
	oopStream.close();
	senseSocket.close();
	actionSocket.close();
	actionSSocket.close();
	}catch(Exception e){}
    }


    void  BindToSocket( int cp ) throws UnknownHostException, IOException
    {
	controlSocket = new Socket(hostname, cp);
//        out = new PrintWriter(kkSocket.getOutputStream(), true);
//        in = new BufferedReader(new InputStreamReader(kkSocket.getInputStream()));
    }


    void  BindToSensesAndActions() throws IOException
    {
	int	c=0;

	for(c=0;c<10;c++)
	{
	    try
	    {
	        senseSocket = new Socket(hostname, commPort);
	    }
	    catch(Exception e)
    	    {
		//server probably not responding. wait a while then try again
		try{Thread.sleep(50);}catch(Exception ie){}
		continue;
	    }

	    break;
	}

	if (c==10)
	{
	    IOException	e = new IOException ("time out");
	    throw(e);
	}

	System.out.println(name + ": successfully opened eyes!");

	//once the sense (input socket has been created, we can create
	//our output socket
	try
	{
	    actionSSocket = new ServerSocket(commPort+1);
	    actionSocket  = actionSSocket.accept();
	}
	catch(IOException e)
	{
	    System.out.println(name + ":");
	    System.out.println(name + ":! unable to create output socket,...maybe i'm inanimate!");
	    //System.exit(1);
	    throw(e);
	}

	System.out.println(name + ": legs are Go!");
    }

    void  GetCommPort() throws IOException, ParseException
    {
	//wait on the server telling us which port we should talk on
	BufferedReader	in;
	String		inputLine;

	in = new BufferedReader(new InputStreamReader(controlSocket.getInputStream()));
	inputLine = in.readLine();

	if (inputLine !=null)
	{
	    DecimalFormat	nf = new DecimalFormat();

	    commPort = nf.parse(inputLine).intValue();
	}

	in.close();
    }

    public void	setActions( DataBlock a )
    {
	if (a==null) 	return;
        long	v = theactions.getVersion();
    	theactions = a;
	theactions.setVersion(v);
	theactions.incrVersion();
    }

    public void	commitActions()
    {
	//send the actions to the server
	//go to copy them or freaky java shit occurs. do they really go over the
	//sockets at all??? IMPROVE
	DataBlock ab = theactions;

	try
	{
	    //ab.incrVersion();
	    DataBlock togo;

	    //actions must be cloneable now , so that regardless of the type
	    //we can send them properly...
	    togo = (DataBlock) theactions.clone();

//	    System.out.println("DEBUG:action type=" + togo.getClass().getName() );

	    oopStream.writeObject( togo );
	    System.out.println(name + ": send actions successfully, v=" + togo.getVersion());
	    togo=null;
	} catch( IOException o )
	{
	    System.out.println(name + ": unable to send actions");
	}
    }

    //more interfacing funcs
    public boolean	sensesChanged()
    {
	if (sw==null)	return false;

        DataBlock	s = (DataBlock) sw.getSenses(this);

	if (s==null)	return false;

        if (s.getVersion() > lastSenseV )
	{
	     thesenses = s;
	     lastSenseV = thesenses.getVersion();
	     return true;
	}
	return false;
    }

    public DataBlock	waitForNewSenses(Object l)
    { 
	DataBlock	d = sw.waitForNewSenses(l); 
	if (d.getVersion() > lastSenseV )
	{
	    thesenses = d;
	    lastSenseV = thesenses.getVersion();
 	}

	return d;
    }

    public DataBlock	getSenses()
    {
	//deprecated ?
	return thesenses;
    }
}

